package com.panopset;

import static com.panopset.Util.*;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.panopset.io.FileProcessor;

/**
 * Implementation details for Util IO functions.
 *
 * @author Karl Dinwiddie
 *
 */
final class UtilIO {

    protected static Object loadObject(Class<?> clazz, File file) {
        Object rtn = null;
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        if (file.exists()) {
            try {
                fis = new FileInputStream(file);
                ois = new ObjectInputStream(fis);
                rtn = ois.readObject();
            } catch (ClassNotFoundException ex) {
                // Delete the file if it no longer matches a class.
                delete(file);
            } catch (InvalidClassException ex) {
                log(ex.getMessage());
            } catch (Exception ex) {
                log(x("Failed to load") + ": " + getCanonicalPath(file));
                log(ex);
            } finally {
                try {
                    if (ois != null) {
                        ois.close();
                    }
                    if (fis != null) {
                        fis.close();
                    }
                } catch (Exception ex) {
                    log(ex);
                }
            }
        }
        if (rtn == null) {
            try {
                rtn = clazz.newInstance();
            } catch (Throwable t) {
                log(t);
                return null;
            }
        }
        return rtn;
    }

    public static final void saveObject(Object object, File file) {
        if (!file.exists()) {
            File p = file.getParentFile();
            mkdirs(p);
        }
        try {
            FileOutputStream fos = new FileOutputStream(file);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(object);
            oos.flush();
            oos.close();
            fos.close();
        } catch (Exception ex) {
            log(x("Failed to save") + ": " + getCanonicalPath(file));
            ex.printStackTrace();
        }
    }

    protected static boolean createParentDirectories(File file) {
        if (file == null) {
            return false;
        }
        if (!file.exists()) {
            File parentDir = file.getParentFile();
            if (parentDir == null) {
                return false;
            }
            if (parentDir.exists())
                return true;
            mkdirs(parentDir);
        }
        return true;
    }

    protected static void copyFile(File from, File to) {
        try {
            mkdirs(to.getParentFile());
            copyStream(new FileInputStream(from), new FileOutputStream(to));
        } catch (Exception ex) {
            log(x("from") + ": " + getCanonicalPath(from));
            log(x("  to") + ": " + getCanonicalPath(to));
            Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    protected static void copyFile(File from, File to, String encoding) {
        try {
            mkdirs(to.getParentFile());
            copyStream(new InputStreamReader(new FileInputStream(from),
                    encoding), new OutputStreamWriter(new FileOutputStream(to),
                    encoding));
        } catch (Exception ex) {
            log(x("from") + ": " + getCanonicalPath(from));
            log(x("  to") + ": " + getCanonicalPath(to));
            Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static void saveStringToFile(String str, File file) {
        if (!createParentDirectories(file)) {
            return;
        }
        try {
            FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(str);
            bw.close();
            fw.close();
        } catch (IOException ex) {
            log(x("file") + ": " + getCanonicalPath(file));
            log(ex);
        }
    }

    protected static void saveBytesToFile(byte[] bytes, File file) {
        if (!createParentDirectories(file)) {
            return;
        }
        try {
            FileOutputStream fw = new FileOutputStream(file);
            BufferedOutputStream bw = new BufferedOutputStream(fw);
            bw.write(bytes);
            bw.close();
            fw.close();
        } catch (IOException ex) {
            log(x("file") + ": " + getCanonicalPath(file));
            log(ex);
        }
    }

    protected static void copyStream(InputStream inputStream,
            OutputStream outputStream) throws IOException {
        BufferedInputStream is = inputStream instanceof BufferedInputStream ? (BufferedInputStream) inputStream
                : new BufferedInputStream(inputStream);
        BufferedOutputStream os = outputStream instanceof BufferedOutputStream ? (BufferedOutputStream) outputStream
                : new BufferedOutputStream(outputStream);
        int b;
        while ((b = is.read()) != -1) {
            os.write(b);
        }
        is.close();
        os.flush();
        os.close();
    }

    protected static void copyStream(InputStreamReader is, OutputStreamWriter os)
            throws IOException {
        char[] buf = new char[1024];
        int len;
        while ((len = is.read(buf)) > 0) {
            os.write(buf, 0, len);
        }
        is.close();
        os.flush();
        os.close();
    }

    protected static void copyLibraryResource(String resourcePath,
            File targetFile) throws IOException {
        try {
            createParentDirectories(targetFile);
            BufferedInputStream bis = new BufferedInputStream(Util.class
                    .getResourceAsStream(resourcePath));
            BufferedOutputStream bos = new BufferedOutputStream(
                    new FileOutputStream(targetFile));
            copyStream(bis, bos);
        } catch (IOException ex) {
            log(x("resourcePath") + ": " + resourcePath);
            log(x("targetFile") + ": " + getCanonicalPath(targetFile));
            Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
            throw(ex);
        }
    }

    protected static Vector<String> loadFile(File file) {
        final Vector<String> lines = new Vector<String>();
        if (file == null) {
            log(x("Can not load null file") + PERIOD);
            return lines;
        }
        if (!file.exists()) {
            log(x("File does not exist") + COLON + SINGLE_SPACE
                    + getCanonicalPath(file));
            return lines;
        }
        new FileProcessor.Builder().file(file).listener(
                new FileProcessor.Listener() {

                    public void finish(FileProcessor processor) {
                        // nothing to do
                    }

                    public void process(FileProcessor processor, String[] s) {
                        lines.add(s[0]);
                    }
                }).construct().exec();
        return lines;
    }

    protected static File savePackageResource(String packagePath,
            String directoryPath) throws IOException {
        String target = directoryPath + "/" + packagePath;
        File tf = new File(target);
        copyLibraryResource(packagePath, tf);
        return tf;
    }

    public static Vector<String> loadLinesFromResource(String resourcePath)
            throws IOException {
        Vector<String> rtn = new Vector<String>();
        BufferedInputStream bis = getResourceStream(resourcePath);
        int i = bis.read();
        StringWriter sw = new StringWriter();
        while (i != -1) {
            if (i == 10 || i == 13) {
                sw = refreshStringWriter(rtn, sw);
            } else {
                sw.append((char) i);
            }
            i = bis.read();
        }
        addIfPopulated(rtn, sw.toString());
        bis.close();
        return rtn;
    }

    public static BufferedInputStream getResourceStream(String resourcePath) {
        return new BufferedInputStream(Util.class
                .getResourceAsStream(resourcePath));
    }

    private static StringWriter refreshStringWriter(Vector<String> v,
            StringWriter sw) {
        addIfPopulated(v, sw.toString());
        return new StringWriter();
    }

    public static void addIfPopulated(Vector<String> v, String s) {
        if (v == null)
            return;
        if (s == null)
            return;
        if (isPopulated(s)) {
            v.add(s);
        }
    }
}
